ЛЕКЦИЯ 3
Ввод/вывод. Операторы.
Ввод и вывод: консоль
Как
мы уже говорили, любой алгоритм должен быть результативным. В общем случае это
означает, что он должен сообщать результат своей работы потребителю:
пользователю-человеку или другой программе (например, программе управления
принтером).
В
программировании существует специальное понятие консоль, которое обозначает
клавиатуру при вводе и монитор при выводе.
Ввод
с консоли
Для
того чтобы получить данные, вводимые пользователем вручную (то есть с консоли),
применяются команды
read(<список_ввода>) и readln(<список_ввода>).
Первая
из этих команд считывает все предложенные ей данные, оставляя курсор в конце
последней строки ввода, а вторая - сразу после окончания ввода переводит курсор
на начало следующей строки. В остальном же их действия полностью совпадают.
Список
ввода - это последовательность имен переменных, разделенных запятыми. Например,
при помощи команды
readln(k,x,c,s); {k:byte; x:real; c:char; s:string}
программа
может получить с клавиатуры данные сразу для четырех переменных, относящихся к
различным типам данных.
Вводимые
значения необходимо разделять пробелами, а завершать ввод - нажатием клавиши Enter. Ввод данных заканчивается в тот момент, когда
последняя переменная из списка ввода получила свое значение. Следовательно, вводя данные при помощи приведенной выше
команды, вы можете нажать Enter четыре раза - после
каждой из вводимых переменных, - либо же только один раз, предварительно введя
все четыре переменные в одну строчку (обязательно нужно разделить их
пробелами).
Типы
вводимых значений должны совпадать с типами указанных переменных, иначе
возникает ошибка. Поэтому нужно внимательно следить за правильностью вводимых
данных.
Вообще,
вводить с клавиатуры можно только данные базовых типов (за исключением
логического).
Если же программе все-таки необходимо получить с консоли значение
для boolean-величины, придется действовать более хитро: вводить оговоренный
символ, а уже на его основе присваивать логической переменной соответствующее
значение. Например:
repeat
writeln('Согласны
ли Вы с этим утверждением? y - да, n - нет');
readln(c); {c:char}
case c of
'y': b:= true;
'n': b:= false;
else writeln('Ошибка!');
end;
until
(c='n')or(c='y');
Второе
исключение: строки, хотя они и не являются базовым типом, вводить тоже
разрешается. Признаком окончания ввода строки является нажатие клавиши Enter, поэтому все следующие за нею переменные необходимо
вводить с новой строчки.
Вывод
на консоль
Сделаем
одно важное замечание: ожидая от человека ввода с клавиатуры, не нужно
полагать, что он окажется ясновидящим и просто по мерцанию курсора на черном
экране догадается, какого типа переменная нужна ожидающей программе. Старайтесь
всегда придерживаться правила: "лысый" ввод недопустим! Перед тем как
считывать что-либо с консоли, необходимо сообщить пользователю, что именно он
должен ввести: смысл вводимой информации, тип данных, максимальное и
минимальное допустимые значения и т.п.
Примером
неплохого приглашения служит, скажем, такая строчка:
Введите
два вещественных числа (0.1<x,y<1000000) -
длины катетов.
Для
того чтобы вывести на экран какое-либо сообщение, воспользуйтесь процедурой
write(<список_вывода>) или writeln(<список_вывода>).
Первая
из них, напечатав на экране все, о чем ее просили, оставит курсор в конце
выведенной строки, а вторая переведет его в начало следующей строчки.
Список
вывода может состоять из нескольких переменных, записанных через запятую; все
эти переменные должны иметь тип либо базовый, либо строчный. Например,
writeln(a,b,c);
Форматный
вывод
Если
для вывода информации воспользоваться командой, приведенной в конце предыдущего
пункта, то выводимые символы окажутся "слепленными". Чтобы этого не
случилось, нужно либо позаботиться о пробелах между выводимыми переменными:
writeln(a,' ',b,' ',c);
либо
задать для всех (или хотя бы для некоторых) переменных формат вывода:
writeln(a:5,b,c:20:5);
Первое
число после знака ":" обозначает количество позиций, выделяемых под
всю переменную, а второе - под дробную часть числа. Десятичная точка тоже
считается отдельным символом.
Если
число длиннее, чем отведенное под него пространство, количество позиций будет
автоматически увеличено. Если же выводимое число короче заданного формата, то
спереди к нему припишутся несколько пробелов.
Например,
если a = 25, b = 'x', а c = 10.5, то после выполнения команды writeln(a:5,' ',b,c:10:5) на экране или в файле будет
записано следующее (подчерки в данном случае служат лишь для визуализации
пробелов):
_
_ _25_x_ _10.50000
Особенно
важен формат при выводе вещественных переменных. К примеру, если не указать
формат, то число 10.5 будет выведено как 1.0500000000Е+0001. Такой формат
называется записью с плавающей точкой.
Если
же задать только общую длину вещественного числа, не указывая длину дробной
части, то оно будет занимать на экране заданное количество символов (в случае
надобности, спереди будет добавлено соответствующее количество пробелов), но
при этом останется в формате плавающей точки. Минимальной длиной для вывода
вещественных чисел является 10 (при формате _x.xE+yyyy).
Первая позиция зарезервирована под знак "-".
Необходимо
помнить, что в случае недостаточной длины вывода число будет автоматически
округлено, например (подчерк служит для визуализации
пробела):
Оператор форматного вывода Результат
вывода на экран
write (125.2367:10); _1.3E+0002
write (125.2367:11); _1.25E+0002
write (125.2367:12); _1.252E+0002
write (125.2367:13); _1.2524E+0002
write (125.2367:14); _1.25237E+0002
write (125.2367:15); _1.252367E+0002
write (125.2367:16); _1.2523670E+0002
Пример
программы на языке Pascal
program start;
var s: string;
begin
write('Пожалуйста,
введите Ваше имя: ');
readln(s);
writeln('Мы рады Вас
приветствовать, ',s,'!');
end.
Во
время работы этой программы на экране появится следующее:
Пожалуйста,
введите Ваше имя: Иван Иваныч
Мы
рады Вас приветствовать, Иван Иваныч!
Комментарии
Большие
логически замкнутые блоки программы удобно разделять строками-комментариями,
содержащими информацию о смысле последующего блока. Комментарий - это строка
(или несколько строк) из произвольных символов, заключенная в фигурные скобки:
{
комментарий }
Другой
вариант оформления комментария:
(*
комментарий *)
Внутри
самого комментария символы } или *) встречаться не должны.
Во
время компилирования программы комментарии игнорируются. Следовательно, их
можно добавлять в любом месте программы. Можно даже разорвать оператор вставкой
комментария. Кроме того, все, что находится после ключевого слова end., завершающего текст программы, компилятор тоже
воспринимает как комментарий.
Пример
программы
program input-output;
{Программа
вводит два целых числа и выводит частное от деления
первого
на второе}
var
n1, n2 : integer; (* переменные целого типа данных *)
x : real;
(* переменная вещественного типа данных *)
begin
write(‘Введите
значение для переменной n1=’);
readln(n1);
write(‘Введите
значение для переменной n2=’);
readln(n2);
x:=n1/n2;
writeln(‘n1/n2=’,
x);
end.
Простейшие операторы
Перейдем
к изучению операторов - специальных конструкций языка Pascal.
Оператором
называется (минимальная) структурно законченная единица программы.
Все
операторы языка Pascal должны заканчиваться знаком
";" (точка с запятой), и ни один оператор не может разрываться этим
знаком. Единственная возможность не ставить после оператора ";"
появляется в том случае, когда сразу за этим оператором следует ключевое слово end.
К
простейшим операторам языка Pascal относятся:
· a:= b; - присваивание переменной а значения переменной
b. В правой части присваивания может находиться переменная, константа,
арифметическое выражение или вызов функции.
· ; - пустой оператор, который можно вставлять куда
угодно, а также вычеркивать откуда угодно, поскольку на целостность программы
это никак не влияет.
· Операторные скобки, превращающие несколько операторов
в один:
begin
<несколько
операторов>
end;
Операторы ветвления
К
операторам, позволяющим из нескольких возможных вариантов выполнения программы
(ветвей) выбрать только один, относятся if и case.
Условный
оператор if
Оператор
if выбирает между двумя вариантами развития событий:
if
<условие>
then <один_оператор>
[else <один_оператор>];
Обратите
внимание, что перед словом else (когда оно
присутствует, конечно же) символ ";" не ставится - ведь это разорвало
бы оператор на две части.
Условный
оператор if работает следующим образом:
Сначала
вычисляется значение <условия> - это может быть любое выражение,
возвращающее значение типа boolean.
Затем,
если в результате получена "истина" (true),
то выполняется оператор, стоящий после ключевого слова then,
а если "ложь" (false) - без дополнительных
проверок выполняется оператор, стоящий после ключевого слова else. Если же else-ветвь отсутствует, то не выполняется
ничего.
Пример.
Программа выводит максимальное из двух чисел.
program Max;
var x, y, max :
integer;
begin
write(‘Введите x’);
read(x);
write(‘Введите max’);
read(max);
if x>max then
y:=x
else y:=max;
writeln(‘Максимальное’, y);
end.
Вложенные
операторы if
В
случае, когда каждый оператор if имеет собственную
else-ветвь, все будет в порядке. А вот если некоторые из них этой ветви не
имеют, может возникнуть ошибка. Компилятор языка Pascal
всегда считает, что else относится к самому ближнему
оператору if. Таким образом, если написать
if i>0
then if s>2
then s:= 1
else s:=
-1;
подразумевая,
что else-ветвь относится к внешнему оператору if, то
компилятор все равно воспримет эту запись как
if i>0
then if s>2
then s:= 1
else s:= -1
else;
Для
того чтобы избежать подобных ошибок, стоит при наличии нескольких вложенных
условных операторов указывать оба ключевых слова, даже если одна из ветвей
будет пустовать.
Итак,
исходный вариант нужно переписать следующим образом:
if i>0
then if s>2
then s:=1
else
else s:=-1;
либо так:
if i>0
then begin if s>2
then s:=1
end
else s:=-1;
Оператор
выбора case
Оператор
case позволяет сделать выбор между несколькими
вариантами:
case
<переключатель> of
<список_констант>
: <один_оператор>;
[<список_констант>
: <один_оператор>;]
[<список_констант>
: <один_оператор>;]
[else <один_оператор>;]
end;
Замечание:
Обратите внимание, что после else двоеточие не
ставится.
Существуют
дополнительные правила, относящиеся к структуре этого оператора:
1. Переключатель должен относиться только к порядковому
типу данных, но не к типу longint.
2. Переключатель может быть переменной или выражением.
3. Список констант может задаваться как явным
перечислением, так и интервалом или их объединением.
4. Повторение констант не допускается.
5. Тип переключателя и типы всех констант должны быть
совместимыми.
Пример
оператора выбора:
program C;
var symbol: char;
writeln(‘Введите символ’);
readln(symbol);
case symbol of
'a'..'z', 'A'..'Z' : writeln('Это латинская буква');
'а'..'я', 'А'..'Я'
: writeln('Это русская буква');
'0'..'9' : writeln('Это
цифра');
' ',#10,#13,#26 : writeln('Это
пробельный символ');
else writeln ('Это служебный символ');
end;
Выполнение
оператора case происходит следующим образом:
1.
вычисляется значение переключателя;
2.
полученный результат проверяется на принадлежность к тому или иному списку
констант;
3.
если такой список найден, то дальнейшие проверки уже не производятся, а
выполняется оператор, соответствующий выбранной ветви, после чего управление
передается оператору, следующему за ключевым словом end,
которое закрывает всю конструкцию case.
4.
если подходящего списка констант нет, то выполняется оператор, стоящий за
ключевым словом else. Если else-ветви нет, то не
выполняется ничего.
Иллюстрация
if и case
В
качестве примера, иллюстрирующего использование операторов ветвления, приведем
несколько различных реализаций функции sgn(x) - знак
числа х. Из математики известно, что эта функция имеет следующие значения:
sgn(x) =
-1, если x < 0;
sgn(x) =
0, если x = 0;
sgn(x) =
1, если x > 0.
Реализовать
эту функцию для случая, когда х вещественное, можно следующими способами (при
условии, что x:real; sgn: -1..1;):
if x=0 then sgn:= 0;
if x<0 then sgn:= -1;
if x>0
then sgn:= 1;
Это
так называемая реализация "в лоб". Здесь нет никаких хитростей и
никаких попыток оптимизации: даже если сработает первый вариант, второй и
третий все равно будут проверены, невзирая на то, что результат уже получен.
if x=0
then sgn:= 0
else if x<0 then sgn:=
-1
else sgn:= 1;
Этот
вариант свободен от излишних проверок в случае, если значение переменной не
положительно. Эту реализацию следует признать более эффективной, чем предыдущая
if x=0
then sgn:=0
else sgn:=x/abs(x);
Еще
одна попытка сократить текст программы. Здесь используется стандартная функция abs(), которая возвращает абсолютное значение аргумента.
Проблема в данном случае состоит в том, что "/" - деление дробное, но
ведь нам необходим целый, а не вещественный ответ! Воспользуемся стандартной
функцией округления.
if x=0
then sgn:=0
else sgn:=round(x/abs(x));
Данный
вариант будет выдавать верный результат.
case x=0 of
true: sgn:=0;
false:
sgn:=round(x/abs(x));
end;
Еще
один (правда, несколько неестественный) способ с использованием оператора
выбора. Вся хитрость этого варианта в том, что выбирающий ветви переключатель
обязан принадлежать к перечислимому типу, именно поэтому пришлось заменить
"х" на "х = 0". Напомним, что эта операция сравнения выдает
результат логического типа boolean, и именно
логические константы true и false
фигурируют в качестве меток выбора.
На
этом простом примере видно, что способов запрограммировать желаемое всегда
больше, чем один, и вряд ли самое простое решение будет и оптимальным.
Метки и безусловный переход
Метка
помечает какое-либо место в тексте программы. Метками могут быть числа от 0 до
9999 или идентификаторы, которые в этом случае уже нельзя использовать для
каких-либо иных нужд. Все метки должны быть описаны в специальном разделе label:
label
<список_всех_меток_через_запятую>;
Меткой
может быть помечен любой оператор программы
<метка>:
<оператор>;
Любая
метка может встретиться в тексте программы только один раз. Используются метки
только операторами безусловного перехода goto:
goto
<метка>;
Это
означает, что сразу после оператора goto будет
выполнен не следующий за ним оператор (как это происходит в обычном случае), а
тот оператор, который помечен соответствующей меткой.
В
принципе, передавать управление можно вперед и назад по тексту программы,
внутрь составных операторов и наружу и т.п. Исключением являются только процедуры
и функции: внутрь них и наружу безусловные переходы невозможны.
Вообще
же использование безусловных переходов в структурном программировании считается
"дурным тоном". Поэтому советуем воздерживаться от употребления
операторов goto. Язык Pascal
обладает достаточным количеством структурных конструкций и возможностей,
позволяющих достичь хороших результатов надежными средствами.
Операторы циклов
Для
того чтобы обработать несколько однотипных элементов, совершить несколько
одинаковых действий и т.п., разумно воспользоваться оператором цикла - любым из
четырех, который наилучшим образом подходит к поставленной задаче.
Оператор
цикла повторяет некоторую последовательность операторов заданное число раз,
которое может быть определено и динамически - уже во время работы программы.
Замечание:
Алгоритмы, построенные только с использованием циклов, называются итеративными
- от слова итерация, которое обозначает повторяемую последовательность
действий.
Операторы циклов for-to и for-downto
В
случае когда количество однотипных действий заранее известно (например,
необходимо обработать все компоненты массива), стоит отдать предпочтение циклу
с параметром (for).
Инкрементный
цикл с параметром
Общий
вид оператора for-to:
for i:= first to last do <оператор>;
Счетчик
i (переменная), нижняя граница first (переменная,
константа или выражение) и верхняя граница last
(переменная, константа или выражение) должны относиться к эквивалентным порядковым
типам данных. Если тип нижней или верхней границы не эквивалентен типу
счетчика, а лишь совместим с ним, то осуществляется неявное приведение типов: значение границы преобразуется к типу счетчика, в результате
чего возможны ошибки.
Цикл
for-to работает следующим образом:
1. вычисляется значение верхней границы last;
2. переменной i присваивается значение нижней границы first;
3. производится проверка условия, что i<=last;
4. если условие истинна, то выполняется <оператор>;
5. значение переменной i увеличивается на единицу;
6. пункты 3-5, составляющие одну итерацию цикла,
выполняются до тех пор, пока i не станет строго больше, чем last;
как только это произошло, выполнение цикла прекращается, а управление
передается следующему за ним оператору.
Из
этой последовательности действий можно понять, какое количество раз отработает
цикл for-to в каждом из трех случаев:
first
< last: цикл будет работать last-first+1 раз;
first = last: цикл отработает ровно один раз;
first
> last: цикл вообще не будет работать.
После окончания работы цикла переменная-счетчик может потерять свое
значение. Таким образом, нельзя с уверенностью утверждать, что после того, как
цикл завершил работу, обязательно окажется, что i=last+1. Поэтому попытки
использовать переменную-счетчик сразу после завершения цикла (без присваивания
ей какого-либо нового значения) могут привести к непредсказуемому поведению
программы при отладке.
Пример
program sum;
{Программа вводит целое положительное число n и подсчитывает
сумму всех целых чисел от 1 до n}
var
i,n,s : integer;
begin
write(‘Введите значение n=’);
readln(n);
s:=0;
for i:=1 to n do
s:=s+i;
writeln(‘Сумма=’,s);
end.
Декрементный
цикл с параметром
Существует
аналогичный вариант цикла for, который позволяет
производить обработку не от меньшего к большему, а в противоположном
направлении:
for i:= first downto last do <оператор>;
Счетчик
i (переменная), верхняя граница first (переменная,
константа или выражение) и нижняя граница last
(переменная, константа или выражение) должны иметь эквивалентные порядковые
типы. Если тип нижней или верхней границы не эквивалентен типу счетчика, а лишь
совместим с ним, то осуществляется неявное приведение типов.
Цикл
for-downto работает следующим образом:
1. переменной i присваивается значение first;
2. производится проверка условия, что i>=last;
3. если условие истинна, то выполняется <оператор>;
4. значение переменной i уменьшается на единицу;
5. пункты 2-4 выполняются до тех пор, пока i не станет
меньше, чем last; как только это произошло, выполнение
цикла прекращается, а управление передается следующему за ним оператору.
Если
при этом
first
< last, то цикл вообще не будет работать;
first = last, то цикл отработает один раз;
first
> last, то цикл будет работать first-last+1 раз.
Замечание
о неопределенности значения счетчика после окончания работы цикла справедливо и
в этом случае.
Операторы
циклов while и repeat-until
Если
заранее неизвестно, сколько раз необходимо выполнить тело цикла, то удобнее
всего пользоваться циклом с предусловием (while) или
циклом с постусловием (repeat-until).
Общий
вид этих операторов таков:
while
<условие_1> do <оператор>;
repeat
<операторы> until <условие_2>;
Условие
окончания цикла может быть выражено переменной, константой или выражением,
имеющим логический тип.
Замечание:
Обратите внимание, что на каждой итерации циклы for и
while выполняют только по одному оператору (либо
группу операторов, заключенную в операторные скобки begin-end
и потому воспринимаемую как единый составной оператор). В отличие от них, цикл repeat-until позволяет выполнить сразу несколько
операторов: ключевые слова repeat и until сами служат операторными скобками.
Так
же, как циклы for-to и for-downto,
циклы while и repeat-until
можно назвать в некотором смысле противоположными друг другу.
Последовательности
действий при выполнении этих циклов таковы:
|
Для while: |
Для repeat-until: |
|
1. Проверяется, истинно ли <условие_1>. |
1. Выполняются <операторы>. |
|
2. Если это так, то выполняется <оператор>. |
2. Проверяется, ложно ли <условие_2> |
|
3. Пункты 1 и 2 выполняются до тех пор, пока <условие_1> не станет ложным. |
3. Пункты 1 и 2 выполняются до тех пор, пока <условие_2> не станет истинным. |
Таким
образом, если <условие_1> изначально ложно, то цикл while
не выполнится ни разу. Если же <условие_2> изначально истинно, то цикл repeat-until выполнится один раз.
Пример.
Дана программа вычисления суммы S первых 100
членов ряда: 1+2+3+...+100.
|
program
W; var k,s: integer; begin s:=0; k:=1; while k<100 do begin s:=s+k; k:=k+1; end; writeln('сумма=',s) end. |
program W; var k,s: integer; begin s:=0; k:=1; repeat s:=s+k; k:=k+1; until k=100; writeln('сумма=',s,'
кол-во шагов к=',k) end. |
|
сумма=4950 кол-во шагов к=100 а если
к<=100, то ответ сумма=5050 кол-во шагов к=101 |
сумма=4950 кол-во шагов к=100 |
break и
continue
Существует
возможность прервать выполнение цикла (или одной его итерации), не дождавшись
конца его (или ее) работы.
break
прерывает работу всего цикла и передает управление на следующий за ним
оператор.
continue
прерывает работу текущей итерации цикла и передает управление следующей
итерации (цикл repeat-until) или на предшествующую ей
проверку (циклы for-to, for-downto,
while).
Замечание:
При прерывании работы циклов for-to и for-downto с помощью функции break
переменная цикла (счетчик) сохраняет свое текущее значение.
Оператор безусловного перехода goto
Возвращаясь к сказанному об операторе goto4), необходимо отметить,
что при всей его нежелательности все-таки существует ситуация, когда
предпочтительно использовать именно этот оператор - как с точки зрения
структурированности текста программы, так и с точки зрения логики ее
построения, и уж тем более с точки зрения уменьшения трудозатрат программиста.
Эта ситуация - необходимость передачи управления изнутри нескольких вложенных
циклов на самый верхний уровень.
Дело в том, что процедуры break и continue прерывают только один цикл - тот, в теле которого
они содержатся. Поэтому в упомянутой выше ситуации пришлось бы заметно
усложнить текст программы, вводя много дополнительных прерываний. А один
оператор goto способен заменить их все.
Сравним два программно-эквивалентных отрывка:
write('Матрица '); write('Матрица ');
for i:=1 to n do for i:=1 to n do
begin for j:=1 to m do
flag:=false;
if a[i,j]>a[i,i]
for j:=1 to m do
then begin
if a[i,j]>a[i,i] write('не ');
then begin flag:=true; goto 1;
write('не '); end;
break;
1: writeln('обладает
end свойством
if flag then break; диагонального
end; преобладания.');
writeln('обладает свойством
диагонального
преобладания.');